package com.winway.jxl.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;

import com.winway.jxl.anomotion.CellColum;
import com.winway.jxl.anomotion.CellEntity;
import com.winway.jxl.util.ClassMenager;
import com.winway.jxl.util.Loger;

import jxl.Cell;
import jxl.CellType;
import jxl.DateCell;
import jxl.Sheet;
import jxl.Workbook;
import jxl.read.biff.BiffException;

/**
 * @see Excel读取器，作用是用来读取Excel文件，把每一行Excel数据封装成实体类列表数据
 * 
 * @author mr-lao
 * 
 * @param <T>
 *            泛型T的字段必需有Colum注解
 */

public class ExcelReader<T> {
	private Workbook mWorkbook;
	// 表头
	private HashMap<String, ContentMessage> headerMap;
	// 数据实体类
	private Class<?> cls;
	// 日期格式
	private SimpleDateFormat sdf;

	/**
	 * 设置时间解释格式
	 */
	public void setDateParser(String pattern) {
		sdf = new SimpleDateFormat(pattern);
	}

	static class ContentMessage {
		public int columIndex;
		public String contentMessage;
		public CellType type;
	}

	public ExcelReader(Class<T> cls, String exlPath) throws IOException, BiffException {
		this(cls, new File(exlPath));
	}

	public ExcelReader(Class<T> cls, File exlFile) throws IOException, BiffException {
		this(cls, new FileInputStream(exlFile));
		// this.mExlFile = exlFile;
	}

	public ExcelReader(Class<T> cls, InputStream in) throws IOException, BiffException {
		mWorkbook = Workbook.getWorkbook(in);
		// sheets = mWorkbook.getSheets();
		this.cls = cls;
		errorMap.clear();
	}

	/**
	 * 重置读取器的Excel数据源
	 * @time 2018年1月15日 下午2:46:24
	 * @param exlPath
	 * @throws BiffException
	 * @throws IOException
	 */
	public void resetInit(String exlPath) throws BiffException, IOException {
		resetInit(new File(exlPath));
	}

	/**
	 * 重置读取器的Excel数据源
	 * @time 2018年1月15日 下午2:47:05
	 * @param exlFile
	 * @throws BiffException
	 * @throws IOException
	 */
	public void resetInit(File exlFile) throws BiffException, IOException {
		resetInit(new FileInputStream(exlFile));
	}

	/**
	 * 重置读取器的Excel数据源
	 * @time 2018年1月15日 下午2:47:11
	 * @param in
	 * @throws BiffException
	 * @throws IOException
	 */
	public void resetInit(InputStream in) throws BiffException, IOException {
		errorMap.clear();
		mWorkbook = Workbook.getWorkbook(in);
		hadClose = false;
	}

	/**
	 * 读取Excel的指定Sheet的数据
	 * 
	 * @param sheet    指定sheet，从0开始
	 * @param headerLine   指定表头从哪一行写起，从0开始
	 * @return
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws ClassNotFoundException
	 * @throws IndexOutOfBoundsException
	 * @throws InvocationTargetException
	 * @throws IllegalArgumentException
	 */
	public List<T> readExcel(int sheet, int headerLine) throws InstantiationException, IllegalAccessException,
			ClassNotFoundException, IllegalArgumentException, InvocationTargetException, IndexOutOfBoundsException {
		return readExcel(mWorkbook.getSheet(sheet), headerLine);
	}

	/**
	 * 读取Excel的指定Sheet的数据
	 * 
	 * @param sheet
	 * @param headerLine
	 * @return
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws ClassNotFoundException
	 * @throws InvocationTargetException
	 * @throws IllegalArgumentException
	 */
	public List<T> readExcel(String sheetName, int headerLine) throws InstantiationException, IllegalAccessException,
			ClassNotFoundException, IllegalArgumentException, InvocationTargetException {
		return readExcel(mWorkbook.getSheet(sheetName), headerLine);
	}

	/**
	 * 读取Excel的指定Sheet的数据
	 * 
	 * @param sheet
	 * @param headerLine
	 * @return
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws ClassNotFoundException
	 * @throws IndexOutOfBoundsException
	 * @throws InvocationTargetException
	 * @throws IllegalArgumentException
	 */
	public List<T> readExcel(int sheet) throws InstantiationException, IllegalAccessException, ClassNotFoundException,
			IllegalArgumentException, InvocationTargetException, IndexOutOfBoundsException {
		return readExcel(mWorkbook.getSheet(sheet), -1000);
	}

	/**
	 * 读取Excel的指定Sheet的数据
	 * 
	 * @param sheet
	 * @param headerLine
	 * @return
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws ClassNotFoundException
	 * @throws IndexOutOfBoundsException
	 * @throws InvocationTargetException
	 * @throws IllegalArgumentException
	 */
	public List<T> readExcel(String sheet) throws InstantiationException, IllegalAccessException,
			ClassNotFoundException, IllegalArgumentException, InvocationTargetException, IndexOutOfBoundsException {
		return readExcel(mWorkbook.getSheet(sheet), -1000);
	}

	/**
	 * 读取Excel的指定Sheet的数据
	 * 
	 * @param sh
	 * @param headerLine
	 * @return
	 * @throws ClassNotFoundException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws IllegalArgumentException
	 */
	@SuppressWarnings("unchecked")
	public List<T> readExcel(Sheet sh, int headerLine) throws ClassNotFoundException, InstantiationException,
			IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		List<T> list = new ArrayList<T>();
		// 解析表头
		if (headerLine >= 0) {
			headerMap = new HashMap<String, ContentMessage>();
			parseHeader(headerMap, sh, headerLine);
		} else {
			headerLine = -1;
		}
		ContentMessage[] readLine = null;
		for (int line = headerLine + 1; line >= 0; line++) {
			readLine = readLine(sh, line);
			if (readLine == null || readLine.length == 0) {
				break;
			}
			T instance = (T) cls.newInstance();
			readCellColum(readLine, instance, headerMap, sdf);
			readCellEntity(readLine, instance, headerMap, sdf);
			list.add(instance);
		}

		// 输出异常信息
		printErrorMSG();
		close();
		return list;
	}

	private static void printErrorMSG() {
		for (String key : errorMap.keySet()) {
			Loger.error(errorMap.get(key));
		}
	}

	/**
	 * 读取Entity
	 * 
	 * @param line
	 * @param instance
	 * @param headerMap
	 * @throws ClassNotFoundException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 * @throws InvocationTargetException
	 */
	private static <T> void readCellEntity(ContentMessage[] line, T instance, HashMap<String, ContentMessage> headerMap,
			SimpleDateFormat sdf) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
			IllegalArgumentException, InvocationTargetException {
		Class<?> cls = instance.getClass();
		HashMap<String, List<Annotation>> annotations = ClassMenager.getAnnotations(cls, ClassMenager.MODE_FIELD,
				CellEntity.class.getName());
		Set<String> keySet = annotations.keySet();
		for (String fieldName : keySet) {
			CellEntity ann = (CellEntity) annotations.get(fieldName).get(0);
			if (ann.excel()) {
				Field field = ClassMenager.getField(cls, fieldName);
				Object newInstance = field.getType().newInstance();
				readCellColum(line, newInstance, headerMap, sdf);
				ClassMenager.getSetMethod(cls, fieldName).invoke(instance, newInstance);
			}
		}
	}

	private static HashMap<String, String> errorMap = new HashMap<>();

	/**
	 * 读取Colum
	 * 
	 * @param line
	 * @param instance
	 * @param headerMap
	 * @throws ClassNotFoundException
	 */
	private static <T> void readCellColum(ContentMessage[] line, T instance, HashMap<String, ContentMessage> headerMap,
			SimpleDateFormat sdf) throws ClassNotFoundException {
		if (null == line || line.length == 0) {
			return;
		}
		Class<?> cls = instance.getClass();
		// 获取列注解
		HashMap<String, List<Annotation>> annotations = ClassMenager.getAnnotations(cls, ClassMenager.MODE_FIELD,
				CellColum.class.getName());
		Set<String> keySet = annotations.keySet();
		for (String fieldName : keySet) {
			StringBuilder headernamesErrorBuilder = null;
			// key表示属性字段
			CellColum colum = (CellColum) annotations.get(fieldName).get(0);
			int columIndex = colum.index();
			if (null != headerMap) {
				// 处理表头
				String headerName = colum.headerName();
				if (!headerMap.containsKey(headerName)) {
					headernamesErrorBuilder = new StringBuilder();
					headernamesErrorBuilder.append(headerName).append(",");
					String[] otherHeaderNames = colum.otherHeaderNames();
					if (otherHeaderNames != null) {
						for (String name : otherHeaderNames) {
							headerName = name;
							if (headerMap.containsKey(headerName)) {
								headernamesErrorBuilder = null;
								break;
							}
						}
					}
					if (headernamesErrorBuilder != null) {
						headernamesErrorBuilder.deleteCharAt(headernamesErrorBuilder.length() - 1);
					}
				}
				// 找出数据对应的列号
				if (!headerMap.containsKey(headerName)) {
					errorMap.put("表头“" + headerName + "”在此Excel文档中不存在",
							"表头“" + headernamesErrorBuilder.toString() + "”在此Excel文档中不存在");
					continue;
				}
				columIndex = headerMap.get(headerName).columIndex;
			}
			if (columIndex >= line.length) {
				return;
			}
			Method method = ClassMenager.getSetMethod(cls, fieldName);
			Class<?>[] parameterTypes = method.getParameterTypes();
			if (parameterTypes[0].equals(List.class)) {
				setListValues(new ArrayList<String>(), line, columIndex, instance, method);
			} else if (parameterTypes[0].equals(ArrayList.class)) {
				setListValues(new ArrayList<String>(), line, columIndex, instance, method);
			} else if (parameterTypes[0].equals(LinkedList.class)) {
				setListValues(new LinkedList<String>(), line, columIndex, instance, method);
			} else {
				ContentMessage contentMessage = line[columIndex];
				ClassMenager.setMethod(instance, method, contentMessage.contentMessage, sdf);
			}
		}
	}

	private static void setListValues(List<String> list, ContentMessage[] line, int columIndex, Object instance,
			Method method) {
		for (int i = columIndex; i < line.length; i++) {
			String value = line[i].contentMessage;
			list.add(value);
		}
		try {
			method.invoke(instance, list);
		} catch (Exception e) {
			Loger.error(e);
		}
	}

	private boolean hadClose = false;

	public void close() {
		if (hadClose) {
			return;
		}
		mWorkbook.close();
	}

	/**
	 * 读取表头
	 * 
	 * @param headerMap
	 * @param sh
	 * @param headerLine
	 */
	private static void parseHeader(HashMap<String, ContentMessage> headerMap, Sheet sh, int headerLine) {
		headerMap.clear();
		// 读取表头行
		ContentMessage[] readLine = readLine(sh, headerLine);
		for (ContentMessage contentMessage : readLine) {
			if (!(null == contentMessage.contentMessage || "".equals(contentMessage.contentMessage.trim()))) {
				Loger.log("header:" + contentMessage.contentMessage + ",colum index:" + contentMessage.columIndex,
						false);
				headerMap.put(contentMessage.contentMessage.trim(), contentMessage);
			}
		}
	}

	private static SimpleDateFormat excelsdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

	/**
	 * 读取指定行的数据
	 * 
	 * @param sh
	 * @param line
	 * @param sheet
	 * @return
	 */
	private static ContentMessage[] readLine(Sheet sh, int line) {
		Cell[] row = null;
		try {
			row = sh.getRow(line);
		} catch (Exception e) {
			Loger.log(e, false);
		}
		if (row == null || row.length == 0) {
			return null;
		}
		ContentMessage[] list = new ContentMessage[row.length];
		for (int i = 0; i < row.length; i++) {
			Cell c = row[i];
			ContentMessage content = new ContentMessage();
			content.contentMessage = c.getContents();
			content.columIndex = c.getColumn();
			content.type = c.getType();
			if (CellType.DATE.equals(content.type)) {
				try {
					DateCell dateCell = (DateCell) c;
					long longtime = dateCell.getDate().getTime();
					TimeZone timeZone = dateCell.getDateFormat().getTimeZone();
					int offset = timeZone.getOffset(longtime);
					long utctime = longtime - offset;
					TimeZone zone = TimeZone.getDefault();
					int bjoffset = zone.getOffset(utctime);
					long localtime = utctime - bjoffset;
					Date localDate = new Date(localtime);
					content.contentMessage = excelsdf.format(localDate);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			list[content.columIndex] = content;
		}
		return list;
	}
}
